/*MyPhotoIndex Free Photo Organizer <www.myphotoindex.com>
  Copyright (C) <2006-2009>  <Asaf Yarkoni asaf@yarkoni.net>

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using MyPhotoIndex.DataAccess;
using System.IO;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using MyPhotoIndex.Utilities;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Data.OleDb;
using System.Data;
using MyPhotoIndex.ImageBank.Properties;
using MyPhotoIndex.ImageBank.Query;
using System.Runtime.Serialization;
using System.Security;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing.Imaging;
using MyPhotoIndex.Utilities.ImageMetaData;
using MyPhotoIndex.ImageCache;
using MyPhotoIndex.Plugins.ImageFormatProviders;
using MyPhotoIndex.Interfaces;
using System.Collections;
using System.Security.Cryptography;
using MyPhotoIndex.Interfaces.Imaging;
using MyPhotoIndex.Plugins.DataProviders;
using System.Text.RegularExpressions;

namespace MyPhotoIndex.ImageBank
{
    //   [Serializable]
    public class Storage : Singleton<Storage>, IImageCollection, IDisposable
    {
        public event EventHandler FilterChangedEvent;
        public event EventHandler<EventArgs> ImageCollectionLoadedEvent;
        public event EventHandler<EventArgs> ImageCollectionLoadingEvent;
        public event EventHandler<ImageCollectionProgressEventArgs> ImageCollectionProgressEvent;

        [NonSerialized]
        private object m_imageLoadingthreadSyncLock = new object();
        private Thread m_thread;
        [NonSerialized]
        private Stack<ImageLoadRequest> m_imageLoadRequests = new Stack<ImageLoadRequest>();
        private Dictionary<int, IImageData> m_imageList = new Dictionary<int, IImageData>();
        private Dictionary<int, IImageData> m_imageFilteredList = new Dictionary<int, IImageData>();
        private List<IImageData> m_imageSortedList = new List<IImageData>();
        private Dictionary<string, IImageData> m_filePathDictionary;
        private object m_filePathDictionarySync = new object();
        private int m_lastEvent = 0;

        private bool m_filterActive;
        [NonSerialized]
        private ApplicativeImageCache m_imageCahce = new ApplicativeImageCache();
        [NonSerialized]
        private ILargeImageProvider m_largeImageCache = new LargeImageCache();

        private bool m_shuttingDown;

        [NonSerialized]
        private IImageSortComparer m_sortingComparer;
        private bool m_sortAscending;

        private static Image ms_sandclock_128 = Resources.sandclock_128;
        private Size m_thumbnailSize = new Size(320, 240);
        private bool m_updateMediaMetaDataOnMediaChange;

        [NonSerialized]
        private Semaphore m_imageLoadSemaphore;
        private readonly int NumberOfImageLoadingThreads;

        private DateTime m_startDate = DateTime.MinValue;
        private DateTime m_endDate = DateTime.MaxValue;
        private List<DateTime> m_validDates = new List<DateTime>();
        private Dictionary<DateTime, int> m_validDatesDictionary = new Dictionary<DateTime, int>();

        private ImageChangeNotificationHandler m_imageChangeNotificationHandler;

        private bool m_filterVersions = true;

        public Storage()
        {
            NumberOfImageLoadingThreads = Environment.ProcessorCount;
            m_imageLoadSemaphore = new Semaphore(NumberOfImageLoadingThreads, NumberOfImageLoadingThreads);
        }

        public bool UpdateMediaMetaDataOnMediaChange
        {
            get { return m_updateMediaMetaDataOnMediaChange; }
            set { m_updateMediaMetaDataOnMediaChange = value; }
        }

        internal ILargeImageProvider LargeImageProvider
        {
            get { return m_largeImageCache; }
            set { m_largeImageCache = value; }
        }

        public DateTime StartDate
        {
            get { return m_startDate; }
            set { m_startDate = value; }
        }

        public DateTime EndDate
        {
            get { return m_endDate; }
            set { m_endDate = value; }
        }

        public List<DateTime> ValidDates
        {
            get { return m_validDates; }
            set { m_validDates = value; }
        }

        public IImageSortComparer SortingComparer
        {
            get { return m_sortingComparer; }
            set { m_sortingComparer = value; }
        }

        public bool SortAscending
        {
            get { return m_sortAscending; }
            set { m_sortAscending = value; }
        }

        public bool ShuttingDown
        {
            get { return m_shuttingDown; }
        }

        public void LoadImageList()
        {
            m_startDate = DateTime.MinValue;
            m_endDate = DateTime.MaxValue;
            m_validDates.Clear();
            m_validDatesDictionary.Clear();

            m_imageList.Clear();
            m_imageFilteredList.Clear();
            m_imageSortedList.Clear();

            if (m_filePathDictionary != null)
            {
                m_filePathDictionary.Clear();
                m_filePathDictionary = null;
            }

            IImagesDataAccessProvider dataProvider = (IImagesDataAccessProvider)DataAccessProvider.Instance;
            int imageCount = dataProvider.GetImageListSize();

            m_imageList = new Dictionary<int, IImageData>(imageCount);
            m_imageFilteredList = new Dictionary<int, IImageData>(imageCount);
            m_imageSortedList = new List<IImageData>(imageCount);

            m_lastEvent = 0;
            using (OleDbDataReader reader = dataProvider.GetPhotoIndexData())
            {
                while (reader.Read())
                {
                    AddOneImageToCollection(reader);
                }
            }

            try
            {
                foreach (IImageData imageData in m_imageList.Values)
                {
                    int versionParent = imageData.VersionParentId;
                    if (versionParent > -1)
                    {
                        IImageData parentImageData = m_imageList[versionParent];
                        parentImageData.VersionsCount++;
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }

            m_imageSortedList.AddRange(m_imageFilteredList.Values);
            if (m_imageCahce != null)
            {
                m_imageCahce.Load(m_imageSortedList);
            }

            if (ImageCollectionLoadedEvent != null)
            {
                ImageCollectionLoadedEvent.Invoke(this, EventArgs.Empty);
            }

            InitFileWatch();
        }

        private void InitFileWatch()
        {
            try
            {
                if (m_imageChangeNotificationHandler != null)
                {
                    m_imageChangeNotificationHandler.Dispose();
                    m_imageChangeNotificationHandler = null;
                }

                m_imageChangeNotificationHandler = new ImageChangeNotificationHandler();
                m_imageChangeNotificationHandler.Init();
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

        public void ScanDiskForChanges()
        {
            if (m_imageChangeNotificationHandler != null)
            {
                m_imageChangeNotificationHandler.UpdateStorageImageFilesStatus();
            }
        }

        private void AddOneImageToCollection(OleDbDataReader reader)
        {
            IImageData imageData = GetImageDataFromRecord(reader);
            AddOneImageToCollection(imageData);
        }

        private void AddOneImageToCollection(IImageData imageData)
        {
            m_imageList.Add(imageData.Id, imageData);

            //Show different versions of the same image.....
            if (imageData.VersionParentId == -1 ||
                (imageData.VersionParentId > -1 && m_filterVersions == false))
            {
                m_imageFilteredList.Add(imageData.Id, imageData);
            }

            if (m_imageList.Count > m_lastEvent + 400 &&
               ImageCollectionLoadingEvent != null)
            {
                ImageCollectionLoadingEvent.Invoke(this, EventArgs.Empty);
                m_lastEvent = m_imageList.Count;
            }
        }

        private IImageData GetImageDataFromRecord(OleDbDataReader reader)
        {
            ImageData imageData = new ImageData(reader.GetInt32((int)ImageDataSourceColumnId.Id));

            //if (reader.IsDBNull((int)ImageDataSourceColumnId.FileSignature) == false)
            //{
            //    imageData.FileSignature = reader.GetString((int)ImageDataSourceColumnId.FileSignature);
            //}

            if (reader.IsDBNull((int)ImageDataSourceColumnId.LastModified) == false)
            {
                imageData.LastModified = reader.GetDateTime((int)ImageDataSourceColumnId.LastModified);
            }

            imageData.ImportBatchNumber = reader.GetString((int)ImageDataSourceColumnId.ImportBatch);
            imageData.FileName = reader.GetString((int)ImageDataSourceColumnId.FullPath);
            if (ApplicationFolders.UseCentralStorage == true)
            {
                imageData.FileName = Path.Combine(ApplicationFolders.CentralStorageLocation, imageData.FileName);
            }

            imageData.ImageSize = new Size(reader.GetInt16((int)ImageDataSourceColumnId.Width), reader.GetInt16((int)ImageDataSourceColumnId.Height));

            if (reader.IsDBNull((int)ImageDataSourceColumnId.DateTaken) == false)
            {
                imageData.DateTaken = reader.GetDateTime((int)ImageDataSourceColumnId.DateTaken);
            }

            if (imageData.DateTaken == DateTime.MinValue)
            {
                if (reader.IsDBNull((int)ImageDataSourceColumnId.FileCreationDate) == false)
                {
                    imageData.DateTaken = reader.GetDateTime((int)ImageDataSourceColumnId.FileCreationDate);
                }
            }

            if (m_startDate > imageData.DateTaken)
            {
                m_startDate = imageData.DateTaken;
            }

            if (m_endDate < imageData.DateTaken)
            {
                m_endDate = imageData.DateTaken;
            }

            if (m_validDatesDictionary.ContainsKey(imageData.DateTaken) == false)
            {
                m_validDates.Add(imageData.DateTaken);
                m_validDatesDictionary.Add(imageData.DateTaken, 0);
            }

            if (reader.IsDBNull((int)ImageDataSourceColumnId.ImageDescription) == false)
            {
                imageData.Comment = reader.GetString((int)ImageDataSourceColumnId.ImageDescription);
            }

            if (reader.IsDBNull((int)ImageDataSourceColumnId.ImageTags) == false)
            {
                byte[] byteArray = new byte[reader.GetBytes((int)ImageDataSourceColumnId.ImageTags, 0, null, 0, Int32.MaxValue)];
                if (byteArray != null &&
                    byteArray.Length > 0)
                {
                    reader.GetBytes((int)ImageDataSourceColumnId.ImageTags, 0, byteArray, 0, byteArray.Length);
                    using (MemoryStream ms = new MemoryStream(byteArray))
                    {
                        BinaryFormatter bf = new BinaryFormatter();
                        try
                        {
                            object intArrray = bf.Deserialize(ms);
                            imageData.AddTags((int[])intArrray);
                        }
                        catch (ArgumentNullException ex)
                        {
                            Logger.Log(ex);
                            imageData.ClearTags();
                        }
                        catch (SerializationException ex)
                        {
                            Logger.Log(ex);
                            imageData.ClearTags();
                        }
                        catch (SecurityException ex)
                        {
                            Logger.Log(ex);
                            imageData.ClearTags();
                        }
                    }
                }
            }

            if (reader.IsDBNull((int)ImageDataSourceColumnId.Rating) == false)
            {
                imageData.Rating = reader.GetByte((int)ImageDataSourceColumnId.Rating);
            }

            if (reader.IsDBNull((int)ImageDataSourceColumnId.Categories) == false)
            {
                int value = reader.GetInt32((int)ImageDataSourceColumnId.Categories);
                imageData.AddCategories(ImageCategoriesConvertor.FromInt(value));
            }

            imageData.LinkStatus = ImageLinkStatus.Valid;

            if (reader.IsDBNull((int)ImageDataSourceColumnId.VersionParentId) == false)
            {
                imageData.VersionParentId = reader.GetInt32((int)ImageDataSourceColumnId.VersionParentId); ;
            }

            if (reader.IsDBNull((int)ImageDataSourceColumnId.VersionDate) == false)
            {
                imageData.VersionDate = reader.GetDateTime((int)ImageDataSourceColumnId.VersionDate);
            }

            if (reader.IsDBNull((int)ImageDataSourceColumnId.SourceLabel) == false)
            {
                imageData.SourceLabel = reader.GetString((int)ImageDataSourceColumnId.SourceLabel);
            }

            return imageData;
        }

        public Size ThumbnailSize
        {
            get { return m_thumbnailSize; }
            set { m_thumbnailSize = value; }
        }

        public void Filter(List<int> imageList)
        {
            m_imageFilteredList.Clear();
            m_imageSortedList.Clear();

            foreach (int imageId in imageList)
            {
                IImageData imageData = m_imageList[imageId];

                //Show different versions of the same image.....
                //if (imageData.VersionParentId == -1 ||
                //    (imageData.VersionParentId > -1 && m_filterVersions == false))
                {
                    m_imageFilteredList.Add(imageId, imageData);
                    m_imageSortedList.Add(imageData);
                }
            }

            m_filterActive = true;

            if (m_sortingComparer != null)
            {
                m_imageSortedList.Sort(m_sortingComparer);
                if (m_sortAscending == true)
                {
                    m_imageSortedList.Reverse();
                }
            }

            if (FilterChangedEvent != null)
            {
                FilterChangedEvent.Invoke(this, EventArgs.Empty);
            }
        }

        public void Filter(QueryParameter queryParameter)
        {
            if (queryParameter == null)
            {
                Filter((QueryParameter[])null);
                return;
            }

            QueryParameter[] queryParametersArray = new QueryParameter[] { queryParameter };
            Storage.Instance.Filter(queryParametersArray);
        }

        public void Filter(QueryParameter[] queryParameters)
        {
            if (queryParameters == null)
            {
                m_imageFilteredList.Clear();
                m_imageSortedList.Clear();
                foreach (ImageData imageData in m_imageList.Values)
                {
                    //Show different versions of the same image.....
                    if (imageData.VersionParentId == -1 ||
                        (imageData.VersionParentId > -1 && m_filterVersions == false))
                    {
                        m_imageFilteredList.Add(imageData.Id, imageData);
                        m_imageSortedList.Add(imageData);
                    }
                }

                m_filterActive = false;
            }
            else
            {
                m_imageFilteredList.Clear();
                m_imageSortedList.Clear();
                QueryBuilder queryBuilder = new QueryBuilder(queryParameters, new QueryHelper(m_imageList), m_filterVersions);
                m_imageFilteredList = queryBuilder.RunQuery();
                foreach (IImageData imageData in m_imageFilteredList.Values)
                {
                    m_imageSortedList.Add(imageData);
                }

                m_filterActive = true;
            }

            if (m_sortingComparer != null)
            {
                m_imageSortedList.Sort(m_sortingComparer);
                if (m_sortAscending == true)
                {
                    m_imageSortedList.Reverse();
                }
            }

            if (FilterChangedEvent != null)
            {
                FilterChangedEvent.Invoke(this, EventArgs.Empty);
            }
        }

        public void ClearFilter()
        {
            Filter((QueryParameter)null);
        }

        public bool FilterActive
        {
            get
            {
                return m_filterActive;
            }
        }

        public void Sort()
        {
            if (m_sortingComparer != null)
            {
                m_imageSortedList.Sort(m_sortingComparer);
                if (m_sortAscending == true)
                {
                    m_imageSortedList.Reverse();
                }
            }
        }

        public Dictionary<int, IImageData> UnFiltered
        {
            get
            {
                return m_imageList;
            }
        }

        public int Count
        {
            get
            {
                return m_imageFilteredList.Count;
            }
        }

        public IImageData this[int imageId]
        {
            get
            {
                try
                {
                    return m_imageList[imageId];
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                }

                return null;
            }
        }

        public IImageData[] ToArray()
        {
            List<IImageData> list = new List<IImageData>(m_imageList.Values);
            return list.ToArray();
        }

        public Image GetThumbnail(IImageData imageData)
        {
            Image thumbnail = GetImageFromCache(imageData.Id, new Size());
            if (thumbnail == null)
            {
                lock (ms_sandclock_128)
                {
                    thumbnail = (Image)ms_sandclock_128.Clone();
                }
                imageData.ThumbnailSize = new Size(thumbnail.Width, thumbnail.Height);
                ThreadPool.QueueUserWorkItem(new WaitCallback(AddThumbnailToCache), imageData);
            }

            return thumbnail;
        }

        private void AddThumbnailToCache(Object data)
        {
            IImageData imageData = (IImageData)data;
            Image thumbnail = imageData.CreateThumbnail(m_thumbnailSize);

            if (thumbnail != null &&
                imageData.FormatProvider.IsCachable == true)
            {
                try
                {
                    if (m_imageCahce != null)
                    {
                        m_imageCahce.Add(imageData.Id, thumbnail);
                    }
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                    thumbnail = Resources.NoThumbnail;
                }
            }
            else
                if (imageData.FormatProvider.IsCachable == true)
                {
                    thumbnail = Resources.NoThumbnail;
                }

            lock (imageData.SyncLock)
            {
                if (imageData.ThumbnailReferences > 0)
                {
                    imageData.Thumbnail = thumbnail;
                    imageData.InvokeChangedEvent();
                }
                else
                {
                    thumbnail.Dispose();
                }
            }
        }

        public void UpdateImageData(IImageData imageData, bool updateExif, bool clearCachedImage)
        {
            if (imageData == null)
            {
                return;
            }

            IImagesDataAccessProvider dataProvider = (IImagesDataAccessProvider)DataAccessProvider.Instance;

            try
            {
                try
                {
                    IImageMetaData origImageData = imageData.FormatProvider.GetImageMetaData(imageData.FileName);
                    if (origImageData != null &&
                        (imageData.ImageSize.Height != origImageData.ImageHeight ||
                         imageData.ImageSize.Width != origImageData.ImageWidth) &&
                         (origImageData.ImageHeight > 0 &&
                         origImageData.ImageWidth > 0))
                    {
                        imageData.ImageSize = new Size(origImageData.ImageWidth, origImageData.ImageHeight);
                    }
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                }

                List<ImageDataSourceColumnId> columns = new List<ImageDataSourceColumnId>();
                List<Object> data = new List<object>();
                columns.Add(ImageDataSourceColumnId.Width);
                data.Add(imageData.ImageSize.Width);

                columns.Add(ImageDataSourceColumnId.Height);
                data.Add(imageData.ImageSize.Height);

                columns.Add(ImageDataSourceColumnId.DateTaken);
                data.Add(imageData.DateTaken);

                columns.Add(ImageDataSourceColumnId.DateDigitized);
                data.Add(imageData.DateDigitized);

                columns.Add(ImageDataSourceColumnId.ImageDescription);
                data.Add(imageData.Comment);

                columns.Add(ImageDataSourceColumnId.FullPath);
                data.Add(imageData.FileName);

                columns.Add(ImageDataSourceColumnId.VersionParentId);
                data.Add(imageData.VersionParentId);

                columns.Add(ImageDataSourceColumnId.VersionDate);
                data.Add(imageData.VersionDate);

                columns.Add(ImageDataSourceColumnId.SourceLabel);
                data.Add(imageData.SourceLabel);

                try
                {
                    if (updateExif == true)
                    {
                        imageData.FormatProvider.SetImageMetaData(imageData, imageData.FileName);
                    }
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                }

                //columns.Add(ImageDataSourceColumnId.FileSignature);
                //data.Add(imageData.FileSignature);

                columns.Add(ImageDataSourceColumnId.LastModified);
                data.Add(imageData.LastModified);

                if (m_filePathDictionary != null)
                {
                    m_filePathDictionary.Clear();
                    m_filePathDictionary = null;
                }

                dataProvider.UpdateImageData(imageData.Id, columns.ToArray(), data.ToArray());
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }

            if (clearCachedImage == true)
            {
                try
                {
                    if (m_imageCahce != null)
                    {
                        m_imageCahce.Remove(imageData.Id);
                    }

                    if (m_largeImageCache != null)
                    {
                        m_largeImageCache.Remove(imageData.Id);
                    }
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                }

                ThreadPool.QueueUserWorkItem(new WaitCallback(AddThumbnailToCache), imageData);
            }
        }

        public void AddRemoveImageTags(ReadOnlyCollection<int> selectedImages, int[] tagIds, bool add)
        {
            foreach (int selectedImage in selectedImages)
            {
                IImageData image = Storage.Instance[selectedImage];

                foreach (int tagId in tagIds)
                {
                    if ((image.TagExists(tagId) == true && add == true) ||
                        (image.TagExists(tagId) == false && add == false))
                    {
                        continue;
                    }

                    if (add == true)
                    {
                        image.AddTags(new int[] { tagId });
                    }
                    else
                    {
                        image.RemoveTag(tagId);
                    }
                }

                using (MemoryStream ms = new MemoryStream())
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    bf.Serialize(ms, image.Tags);
                    try
                    {
                        ((IImagesDataAccessProvider)(DataAccessProvider.Instance)).UpdateImageData(image.Id, ImageDataSourceColumnId.ImageTags, ms.ToArray());
                    }
                    catch (Exception ex)
                    {
                        Logger.Log(ex);
                    }
                }

                image.InvokeChangedEvent();
                if (image.VersionsCount > 0)
                {
                    List<int> imageVersions = new List<int>();
                    foreach (IImageData imageVersion in Instance.m_imageList.Values)
                    {
                        if (imageVersion.VersionParentId == image.Id)
                        {
                            imageVersions.Add(imageVersion.Id);
                        }
                    }

                    AddRemoveImageTags(imageVersions.AsReadOnly(), tagIds, add);
                }
            }
        }

        public void UpdateImageRating(ReadOnlyCollection<int> selectedImages, int rating)
        {
            if (rating > 5 ||
                rating < 0)
            {
                return;
            }

            foreach (int selectedImage in selectedImages)
            {
                IImageData image = Storage.Instance[selectedImage];
                if (image.Rating == rating)
                {
                    continue;
                }

                ((IImagesDataAccessProvider)(DataAccessProvider.Instance)).UpdateImageData(image.Id, ImageDataSourceColumnId.Rating, rating);
                image.Rating = rating;
                image.InvokeChangedEvent();

                if (image.VersionsCount > 0)
                {
                    List<int> imageVersions = new List<int>();
                    foreach (IImageData imageVersion in Instance.m_imageList.Values)
                    {
                        if (imageVersion.VersionParentId == image.Id)
                        {
                            imageVersions.Add(imageVersion.Id);
                        }
                    }

                    UpdateImageRating(imageVersions.AsReadOnly(), rating);
                }
            }
        }

        public void UpdateImageCategories(ReadOnlyCollection<int> selectedImages, int categoryId, bool add)
        {
            foreach (int selectedImage in selectedImages)
            {
                IImageData image = Storage.Instance[selectedImage];

                if ((image.CategoryExists(categoryId) == true && add == true) ||
                   (image.CategoryExists(categoryId) == false && add == false) ||
                   (add == true && image.Categories.Length == 4))
                {
                    continue;
                }

                if (add == true)
                {
                    image.AddCategories(new int[] { categoryId });
                }
                else
                {
                    image.RemoveCategory(categoryId);
                }

                try
                {
                    ((IImagesDataAccessProvider)(DataAccessProvider.Instance)).UpdateImageData(image.Id, ImageDataSourceColumnId.Categories, ImageCategoriesConvertor.ToInt(image.Categories));
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                }

                image.InvokeChangedEvent();

                if (image.VersionsCount > 0)
                {
                    List<int> imageVersions = new List<int>();
                    foreach (IImageData imageVersion in Instance.m_imageList.Values)
                    {
                        if (imageVersion.VersionParentId == image.Id)
                        {
                            imageVersions.Add(imageVersion.Id);
                        }
                    }

                    UpdateImageCategories(imageVersions.AsReadOnly(), categoryId, add);
                }
            }
        }

        public void LoadImageFromDisk(IImageData imageData, Size requestedSize, ImageLoadDelegate imageLoadDelegate)
        {
            LoadImageFromDisk(imageData, requestedSize, imageLoadDelegate, null);
        }

        public void LoadImageFromDisk(IImageData imageData, Size requestedSize, ImageLoadDelegate imageLoadDelegate, object tag)
        {
            Image image = GetImageFromCache(imageData.Id, requestedSize);
            if (image != null)
            {
                if (imageLoadDelegate != null)
                {
                    imageLoadDelegate.Invoke(imageData, image, tag);
                }
            }

            m_imageLoadRequests.Push(new ImageLoadRequest(imageData, requestedSize, imageLoadDelegate, tag));
            if (m_thread == null)
            {
                lock (m_imageLoadingthreadSyncLock)
                {
                    if (m_thread == null)
                    {
                        m_thread = new Thread(LoadImageFromDiskAsync);
                        m_thread.Start();
                    }
                }
            }
        }

        internal void LoadImageFromDiskAsync()
        {
            while (m_imageLoadRequests.Count > 0 && m_shuttingDown == false)
            {
                ImageLoadRequest oneLoadRequest = m_imageLoadRequests.Pop();
                if (oneLoadRequest != null)
                {
                    if (m_imageLoadSemaphore.SafeWaitHandle != null &&
                        m_imageLoadSemaphore.SafeWaitHandle.IsClosed == false)
                    {
                        m_imageLoadSemaphore.WaitOne();
                        ThreadPool.QueueUserWorkItem(new WaitCallback(LoadOneImageFromDisk), oneLoadRequest);
                    }
                }
            }

            lock (m_imageLoadingthreadSyncLock)
            {
                m_thread = null;
            }
        }

        internal void LoadOneImageFromDisk(Object parameter)
        {
            Thread.CurrentThread.Priority = ThreadPriority.Lowest;
            try
            {
                ImageLoadRequest imageLoadRequest = (ImageLoadRequest)parameter;
                IImageData imageData = imageLoadRequest.ImageData;

                Image image = LoadCachedImageFromDisk(imageData, imageLoadRequest.RequestedSize);
                if (image == null)
                {
                    imageData.LinkStatus = ImageLinkStatus.Invalid;
                }
                else
                {
                    if (imageLoadRequest.ImageLoadDelegate != null)
                    {
                        imageLoadRequest.ImageLoadDelegate.Invoke(imageData, image, imageLoadRequest.Tag);
                    }
                    else
                    {
                        if (image != null)
                        {
                            image.Dispose();
                        }
                    }
                }
            }
            finally
            {
                try
                {
                    m_imageLoadSemaphore.Release();
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                }
            }
        }

        public Image GetImageFromCache(int imageId, Size size)
        {
            if (m_imageCahce == null)
            {
                return null;
            }

            return m_imageCahce.Find(imageId, size);
        }

        private Image LoadCachedImageFromDisk(IImageData imageData, Size requestedSize)
        {
            bool imageCached = false;

            Image image = GetImageFromCache(imageData.Id, requestedSize);
            bool isLandscape = ImageAlgo.IsLandscapeImage(requestedSize);
            if (image != null)
            {
                imageCached = true;
                if ((isLandscape == true && requestedSize.Width <= m_thumbnailSize.Width) ||
                    (isLandscape == false && requestedSize.Height <= m_thumbnailSize.Width))
                {
                    requestedSize = ImageAlgo.GetImageProportionalSize(image.Size, new SizeF(m_thumbnailSize.Width, m_thumbnailSize.Height));
                    imageData.Thumbnail = new Bitmap(image, requestedSize);
                }

                return image;
            }

            if ((isLandscape == true && requestedSize.Width < m_thumbnailSize.Width) ||
                (isLandscape == false && requestedSize.Height < m_thumbnailSize.Height))
            {
                requestedSize = ImageAlgo.GetImageProportionalSize(imageData.ImageSize, new SizeF(m_thumbnailSize.Width, m_thumbnailSize.Height));
            }

            image = m_largeImageCache.GetLargeImage(imageData, requestedSize, m_imageSortedList);

            if (image != null &&
                m_imageCahce != null &&
                imageData.FormatProvider.IsCachable == true &&
                imageCached == false)
            {
                if ((requestedSize.Width >= requestedSize.Height && requestedSize.Width >= m_thumbnailSize.Width) ||
                    (isLandscape == false && requestedSize.Height >= m_thumbnailSize.Height))
                {
                    using (Image cachedImage = m_imageCahce.Find(imageData.Id, m_thumbnailSize))
                    {
                        if (cachedImage == null)
                        {
                            requestedSize = ImageAlgo.GetImageProportionalSize(image.Size, new SizeF(m_thumbnailSize.Width, m_thumbnailSize.Height));
                            Image thumbnail = new Bitmap(image, requestedSize);
                            m_imageCahce.Add(imageData.Id, thumbnail);

                            imageData.Thumbnail = thumbnail;
                        }
                    }
                }
            }

            return image;
        }

        public void Remove(int imageId)
        {
            Remove(imageId, false);
        }

        public void Remove(int imageId, bool deleteFromMedia)
        {
            IImageData imageData = m_imageList[imageId];
            if (imageData == null)
            {
                Logger.Log(String.Concat("Image data not found id=", imageId));
                return;
            }

            if (deleteFromMedia == true &&
                imageData.FormatProvider.CanDelete(imageData.FileName) == true)
            {
                try
                {
                    imageData.FormatProvider.Delete(imageData.FileName);
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                }
            }

            if (imageData.VersionParentId > -1)
            {
                m_imageList[imageData.VersionParentId].VersionsCount--;
            }

            if (imageData.VersionsCount > 0)
            {
                foreach (IImageData relatedImages in m_imageList.Values)
                {
                    if (relatedImages.VersionParentId == imageData.Id)
                    {
                        relatedImages.VersionParentId = -1;
                        relatedImages.VersionDate = DateTime.MinValue;
                        relatedImages.Update(false, false);
                    }
                }
            }

            ((IImagesDataAccessProvider)(DataAccessProvider.Instance)).RemoveImage(imageData.Id);
            m_imageList.Remove(imageId);
            m_imageFilteredList.Remove(imageId);
            m_imageSortedList.Remove(imageData);
            if (m_imageCahce != null)
            {
                m_imageCahce.Remove(imageData.Id);
            }

            if (m_filePathDictionary != null)
            {
                m_filePathDictionary.Clear();
                m_filePathDictionary = null;
            }
        }

        public int Add(IImageData imageData)
        {
            return Add(imageData, false);
        }

        public virtual int Add(IImageData imageData, bool addToProvider)
        {
            ImageData newImageData = new ImageData(imageData);

            //if (true == ApplicationFolders.UseCentralStorage)
            //{
            //    newImageData.FileName = Path.Combine(ApplicationFolders.CentralStorageLocation, newImageData.FileName);
            //}

            if (addToProvider == true)
            {
                using (MyPhotoIndexDataSet.ImagesDataTable dataTable = new MyPhotoIndexDataSet.ImagesDataTable())
                {
                    byte[] imageTagsData = null;
                    using (MemoryStream ms = new MemoryStream())
                    {
                        BinaryFormatter bf = new BinaryFormatter();
                        bf.Serialize(ms, imageData.Tags);
                        imageTagsData = ms.ToArray();
                    }

                    IImageMetaData metaData = imageData.FormatProvider.GetImageMetaData(imageData.FileName);

                    MyPhotoIndexDataSet.ImagesRow newRow = dataTable.AddImagesRow(imageData.ImportBatchNumber, imageData.FileName, imageData.DateTaken, imageData.Comment,
                                            metaData.CameraMaker, metaData.CameraModel, (short)-1,
                                            (short)-1, (short)-1, metaData.SoftwareUsed,
                                            metaData.LastModifiedDateTime, 0/*imageData.ExposureTime*/, metaData.Aperture,
                                            -1, (short)metaData.ExposureProgram, metaData.IsoSpeed,
                                            imageData.DateTaken, imageData.DateDigitized, -1,
                                            metaData.ExposureCompensation, (short)metaData.MeteringMode, (short)metaData.LightSource,
                                            (short)metaData.FlashStatus, metaData.FocalLength,
                                            (short)imageData.ImageSize.Width, (short)imageData.ImageSize.Height, imageTagsData,
                                            imageData.Rating, ImageCategoriesConvertor.ToInt(imageData.Categories), imageData.VersionParentId,
                                            imageData.VersionDate, imageData.SourceLabel);

                    DataAccessProvider.Instance.UpdateDataTable(dataTable, true, true);
                    newImageData.Id = newRow.Id;
                }
            }

            m_imageList.Add(newImageData.Id, newImageData);

            return newImageData.Id;
        }

        #region IDisposable Members

        ~Storage()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (m_imageChangeNotificationHandler != null)
                {
                    m_imageChangeNotificationHandler.Dispose();
                    m_imageChangeNotificationHandler = null;
                }

                if (m_imageCahce != null)
                {
                    m_imageCahce.Dispose();
                    m_imageCahce = null;
                }

                m_imageLoadSemaphore.Close();

                m_shuttingDown = true;
            }
        }

        #endregion

        #region IEnumerable<ImageData> Members

        public IEnumerator<IImageData> GetEnumerator()
        {
            return m_imageSortedList.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        public void AddCahcedThumbnail(int imageId, Image thumbnail)
        {
            if (m_imageCahce != null)
            {
                m_imageCahce.Add(imageId, thumbnail);
            }
        }

        public IImageData FindByPath(string fileName)
        {
            if (m_filePathDictionary == null)
            {
                lock (m_filePathDictionarySync)
                {
                    if (m_filePathDictionary == null)
                    {
                        Dictionary<string, IImageData> filePathDictionary = new Dictionary<string, IImageData>();
                        foreach (IImageData imageData in m_imageList.Values)
                        {
                            filePathDictionary.Add(imageData.FileName, imageData);
                        }

                        m_filePathDictionary = filePathDictionary;
                    }
                }
            }

            IImageData returnImageData = null;
            m_filePathDictionary.TryGetValue(fileName, out returnImageData);

            return returnImageData;
        }

        public void ReindexCache()
        {
            if (m_imageCahce != null)
            {
                m_imageCahce.Reindex(new MyPhotoIndex.ImageBank.ApplicativeImageCache.ProgressDelegate(ProgressDelegate));
            }
            ImageCollectionProgressEvent = null;
        }

        public void RefreshCache()
        {
            if (m_imageCahce != null)
            {
                m_imageCahce.Clear();
            }

            if (m_largeImageCache != null)
            {
                m_largeImageCache.Clear();
            }
        }

        internal void ProgressDelegate(int maximum, int value)
        {
            if (ImageCollectionProgressEvent != null)
            {
                ImageCollectionProgressEvent.Invoke(this, new ImageCollectionProgressEventArgs(maximum, value));
            }
        }

        public long GetCachePhysicalSizeBytes()
        {
            if (m_imageCahce != null)
            {
                return m_imageCahce.GetPhysicalSizeBytes();
            }

            return -1;
        }

        public long GetCacheFreeSpaceBytes()
        {
            if (m_imageCahce != null)
            {
                return m_imageCahce.GetFreeSpaceBytes();
            }

            return -1;
        }

        public void UseImageCache(bool useCache)
        {
            if ((m_imageCahce == null && useCache == false) ||
               (m_imageCahce != null && useCache == true))
            {
                return;
            }

            if (useCache == true)
            {
                m_imageCahce = new ApplicativeImageCache();
                m_imageCahce.Load(m_imageSortedList);
            }
            else
            {
                m_imageCahce.Dispose();
                m_imageCahce = null;
            }
        }

        public bool CreateNewVersionSet(IImageData[] images)
        {
            try
            {
                if (images == null ||
                   images.Length == 0)
                {
                    return false;
                }

                int numberOfPrimarySets = 0;
                foreach (IImageData imageData in images)
                {
                    if (imageData.VersionsCount > 0)
                    {
                        numberOfPrimarySets++;
                    }
                }

                if (numberOfPrimarySets > 1)
                {
                    //can't use more then one version set at once...
                    return false;
                }

                bool isValid = false;
                foreach (IImageData imageData in images)
                {
                    if (imageData.VersionParentId < 0)
                    {
                        isValid = true;
                        break;
                    }
                }

                if (isValid == false)
                {// all images allready belong to a vesion set...
                    return false;
                }

                int versionSetParentId = -1;
                foreach (IImageData imageData in images)
                {
                    if (imageData.VersionParentId > 0)
                    {
                        versionSetParentId = imageData.VersionParentId;
                        break;
                    }
                    else
                        if (imageData.VersionsCount > 0)
                        {
                            versionSetParentId = imageData.Id;
                            break;
                        }
                }

                if (versionSetParentId == -1)
                {
                    //first image will be made the primary version set image
                    versionSetParentId = images[0].Id;
                }

                IImageData primaryImage = m_imageList[versionSetParentId];

                foreach (IImageData imageData in images)
                {
                    if (imageData.Id != versionSetParentId)
                    {
                        imageData.VersionParentId = versionSetParentId;
                        imageData.Update(false, false);
                        primaryImage.VersionsCount++;

                        imageData.InvokeChangedEvent();
                    }
                }

                primaryImage.InvokeChangedEvent();
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
                return false;
            }

            return true;
        }

        public bool BreakVersionSet(IImageData[] images)
        {
            try
            {
                if (images == null ||
                   images.Length == 0)
                {
                    return false;
                }

                // handle child version images
                foreach (IImageData imageData in images)
                {
                    if (imageData.VersionParentId != -1)
                    {
                        IImageData parent = m_imageList[imageData.VersionParentId];
                        if (parent != null)
                        {
                            parent.VersionsCount--;
                            imageData.VersionParentId = -1;
                            imageData.Update(false, false);

                            imageData.InvokeChangedEvent();
                            parent.InvokeChangedEvent();
                        }
                    }
                }

                // if we still have version masters/parents here then break all of their sets....
                foreach (IImageData imageData in images)
                {
                    if (imageData.VersionsCount > 0)
                    {
                        foreach (IImageData childImageData in m_imageList.Values)
                        {
                            if (childImageData.VersionParentId == imageData.Id)
                            {
                                imageData.VersionsCount--;
                                childImageData.VersionParentId = -1;
                                childImageData.Update(false, false);

                                childImageData.InvokeChangedEvent();
                                imageData.InvokeChangedEvent();
                            }
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                Logger.Log(ex);
                return false;
            }

            return true;
        }

        public bool CreateNewVersion(IImageData imageData, DateTime dateTime)
        {
            try
            {
                IImageData versionSetParent = null;
                //image version sets are not nested so make sure we add the new version to the original set parent
                if (imageData.VersionParentId > -1)
                {
                    versionSetParent = m_imageList[imageData.VersionParentId];
                }
                else
                {
                    versionSetParent = imageData;
                }

                string fileName = Path.GetFileNameWithoutExtension(versionSetParent.FileName);
                fileName += "_v" + dateTime.ToString("yyyyMMddhhmmss");
                string newFileName = Path.ChangeExtension(fileName, Path.GetExtension(versionSetParent.FileName));
                string newMediaPath = Path.Combine(Path.GetDirectoryName(versionSetParent.FileName), newFileName);

                // we allways copy the original image that was edited
                imageData.FormatProvider.CopyTo(imageData, newMediaPath);

                ImageData newImageData = new ImageData(imageData);
                newImageData.FileName = newMediaPath;
                newImageData.VersionDate = dateTime;
                newImageData.VersionParentId = versionSetParent.Id;
                this.Add(newImageData, true);

                versionSetParent.VersionsCount++;
                versionSetParent.InvokeChangedEvent();
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
                return false;
            }

            return true;
        }

        public bool SetAsPrimaryInVersion(IImageData imageData)
        {
            try
            {
                if (imageData.VersionParentId < 0)
                {
                    return false;
                }

                IImageData parentImage = m_imageList[imageData.VersionParentId];
                parentImage.VersionParentId = imageData.Id;
                parentImage.VersionsCount = 0;
                imageData.VersionsCount = 1;
                imageData.VersionParentId = -1;

                foreach (IImageData relatedImages in m_imageList.Values)
                {
                    if (relatedImages.VersionParentId == parentImage.Id)
                    {
                        relatedImages.VersionParentId = imageData.Id;
                        relatedImages.Update(false, false);
                        imageData.VersionsCount++;
                    }
                }

                imageData.Update(false, false);
                parentImage.Update(false, false);

                imageData.InvokeChangedEvent();
                parentImage.InvokeChangedEvent();
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
                return false;
            }


            return true;
        }

        public bool ImageCacheNeedUpgrade()
        {
            if (m_imageCahce != null)
            {
                return m_imageCahce.NeedUpgrade();
            }

            return false;
        }

        public void UpgradeImageCache()
        {
            if (m_imageCahce != null)
            {
                m_imageCahce.UpgradeImageCache(new MyPhotoIndex.ImageBank.ApplicativeImageCache.ProgressDelegate(ProgressDelegate));
            }
        }
    }
}
